package com.baidu.unbiz.easymapper;

import com.baidu.unbiz.easymapper.annotation.CopyBean;
import com.baidu.unbiz.easymapper.annotation.CopyBeanProperty;
import com.baidu.unbiz.easymapper.annotation.FormatType;
import com.google.common.collect.Sets;
import org.apache.commons.lang3.time.FastDateFormat;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.*;

/**
 *  提供 bean copy 的工具类
 *
 * @author itors
 */
public class BeanCopyUtil {

    private static final Logger log = LoggerFactory.getLogger(BeanCopyUtil.class);
    private static final Set<String> mapCaches = Sets.newConcurrentHashSet();
    private static final String METHOD_NAME = "getDescribe";

    /**
     * copy bean  List<A> to List<B>
     *
     * @param aList
     * @param targetClazz
     * @param <B>
     * @return
     */
    public static <A,B> List<B> mapObjectList(List<A> aList , Class<B> targetClazz) {
        if(null == aList || aList.isEmpty()){
            return null;
        }
        List<B> targets = new ArrayList<>(aList.size());
        aList.forEach(tempA ->
            targets.add(mapObject(tempA, targetClazz))
        );
        return targets;
    }

    /**
     * copy bean A to B
     *
     * @param o
     * @param targetClazz
     * @param <B>
     * @return
     */
    public static <B> B mapObject(Object o, Class<B> targetClazz) {
        String copyClazzKey = o.getClass().getName() + "_" + targetClazz.getName();
        log.debug(" copy {} ", copyClazzKey);
        // 不存在 先注册
        if (!mapCaches.contains(copyClazzKey)) {
            log.debug(" {} is not registered.", copyClazzKey);
            mapperRegister(o.getClass(), targetClazz);
        }
        return MapperFactory.getCopyByRefMapper().map(o, targetClazz);
    }

    /**
     * 注册 映射策略
     *
     * @param sourceClazz
     * @param targetClazz
     */
    private static void mapperRegister(Class<?> sourceClazz, Class<?> targetClazz) {
        /**
         * 排除集合类
         */
        if ((Collection.class.isAssignableFrom(sourceClazz))
                || (Map.class.isAssignableFrom(sourceClazz))
                || (Iterator.class.isAssignableFrom(sourceClazz))) {
            return;
        }
        ClassMapBuilder mapBuilder = MapperFactory.getCopyByRefMapper().mapClass(sourceClazz, targetClazz);

        CopyBean copyBean = targetClazz.getAnnotation(CopyBean.class);
        if (null != copyBean && sourceClazz.getName().equals(copyBean.sourceClass().getName())) {
            /**
             * 需要排除的字段
             */
            mapBuilder.exclude(copyBean.excludeFeilds());
            /**
             * 是否非空nopy
             */
            mapBuilder.mapOnNull(copyBean.mapOnNull());
        }
        // 需要映射的字段及其他规则
        Field[] targetClazzFields = getClassFields(targetClazz);
        if (null != targetClazzFields) {
            for (int i = 0, a = targetClazzFields.length; i < a; i++) {
                CopyBeanProperty copyBeanProperty = targetClazzFields[i].getAnnotation(CopyBeanProperty.class);
                if (null != copyBeanProperty) {
                    mapBuilderField(mapBuilder, targetClazzFields[i], copyBeanProperty);
                }
            }
        }
        mapBuilder.register();
        mapCaches.add(sourceClazz.getName() + "_" + targetClazz.getName());
    }

    /**
     * 映射属性
     *
     * @param mapBuilder
     * @param filed
     * @param copyBeanProperty
     */
    private static void mapBuilderField(ClassMapBuilder mapBuilder, Field filed, CopyBeanProperty copyBeanProperty) {
        String sourceField = copyBeanProperty.sourceField();
        FormatType formateType = copyBeanProperty.sourceFieldFormatType();
        String dateFormat = copyBeanProperty.dateFormat();
        Class enumClzz = copyBeanProperty.enumFormatClazz();
        int time = copyBeanProperty.times();
        int precision = copyBeanProperty.precision();
        // 格式化时间
        if (formateType.equals(FormatType.DATE_TIME)) {
            log.debug("start to formate datetime.");
            mapBuilder.rField(
                    sourceField,
                    filed.getName(),
                    s ->
                       FastDateFormat.getInstance(dateFormat).format(s)
                    );
            // 枚举
        } else if (formateType.equals(FormatType.ENUM_FORMAT)) {
            log.debug("start to formate enum.");
            mapBuilder.rField(
                    sourceField,
                    filed.getName(),
                    (source) -> {
                        try {
                            Method method = enumClzz.getMethod(METHOD_NAME, Object.class);
                            return method.invoke(enumClzz.getEnumConstants(), source);
                        } catch (NoSuchMethodException e) {
                            e.printStackTrace();
                        } catch (IllegalAccessException e) {
                            e.printStackTrace();
                        } catch (InvocationTargetException e) {
                            e.printStackTrace();
                        }
                        return null;
                    });
        }else if(formateType.equals(FormatType.SHRINK)) {

            System.out.println();

            log.debug("start to formate SHRINK.");
            mapBuilder.rField(
                    sourceField,
                    filed.getName(),
                    s -> {
                        BigDecimal bigDecimal = new BigDecimal(String.valueOf(s));
                        return String.valueOf(bigDecimal.divide(new BigDecimal(time), precision, RoundingMode.HALF_UP));
                    }
            );


        }else if(formateType.equals(FormatType.MAGNIFY)) {
            log.debug("start to formate MAGNIFY.");
            mapBuilder.rField(
                    sourceField,
                    filed.getName(),
                    s -> {
                        BigDecimal bigDecimal = new BigDecimal(String.valueOf(s));
                        bigDecimal = bigDecimal.multiply(new BigDecimal(time));
                        if(precision != 0){
                            return  String.valueOf(bigDecimal.divide(new BigDecimal(1), precision, RoundingMode.HALF_UP));
                        }else{
                            return String.valueOf(bigDecimal);
                        }
                    }
            );
        }else{
            mapBuilder.field(sourceField, filed.getName());
        }
    }

    /**
     * 清空缓存
     */
    public static void clear(){
        MapperFactory.getCopyByRefMapper().clear();
    }


    /**
     * 获取属性
     *
     * @param clazz
     * @return
     */
    private static Field[] getClassFields(Class clazz) {
        if ((clazz == null) || (clazz.equals(Object.class))) {
            return null;
        }
        Field[] declaredFields = clazz.getDeclaredFields();
        Field[] afterFields = getClassFields(clazz.getSuperclass());
        if (afterFields == null) {
            return declaredFields;
        }
        return joinFields(afterFields, declaredFields);
    }

    private static Field[] joinFields(Field[] field1, Field[] field2) {
        Field[] fields = new Field[field1.length + field2.length];
        System.arraycopy(field1, 0, fields, 0, field1.length);
        System.arraycopy(field2, 0, fields, field1.length, field2.length);
        return fields;
    }
}
